﻿namespace Blip.API
{
    using System;
    using System.Text;
    using System.Collections.Generic;

    /// <summary>
    /// Klasa implementująca interfejs API, tworząca adresy zapytań do serwisu Blip.
    /// </summary>
    /// <seealso cref="IBlipAPI"/>
    /// <seealso cref="IBlipCaller"/>
    public class BlipAPI : IBlipAPI
    {
        /// <summary>
        /// Prefiks adresu do którego wysyłane są żądania.
        /// </summary>
        protected const string URL = "http://api.blip.pl";

        private IBlipCaller api = new BlipCaller();

        private IAuthInfo authInfo;

        private StringBuilder include = new StringBuilder();

        /// <summary>
        /// Konstruktor bezparametrowy inicjalizujący obiekt do komunikacji nie wymagającej uwierzytelnienia.
        /// </summary>
        /// <example>
        /// <code>
        /// BlipAPI api = new BlipAPI();
        /// api.GetAllStatuses(new BlipResponse(ReadResponse));
        /// </code>
        /// </example>
        public BlipAPI()
            : this(string.Empty, string.Empty)
        {

        }

        /// <summary>
        /// Konstuktor inicjalizujący obiekt do zapytań wymagających uwierzytelnienia.
        /// </summary>
        /// <param name="username">Nazwa użytkownika serwisu Blip</param>
        /// <param name="password">Hasło powiązane z kontem</param>
        /// <example>
        /// <code>
        /// BlipAPI api = new BlipAPI("nazwa_użytkownika", "hasło_użytkownika");
        /// api.GetStatuses(new BlipResponse(ReadResponse));
        /// </code>
        /// </example>
        public BlipAPI(string username, string password)
        {
            authInfo = new AuthInfo(username, password);
        }

        /// <summary>
        /// Metoda pomocnicza, tworząca słownik par (string, string).
        /// </summary>
        /// <returns>Utworzony pusty słownik</returns>
        protected virtual IDictionary<string, string> Dictionary()
        {
            return new Dictionary<string, string>();
        }

        /// <summary>
        /// Metoda pomocnicza, generująca pary (string, string) z przekazanych argumentów.
        /// </summary>
        /// <param name="key">Klucz pary</param>
        /// <param name="value">Wartość pary</param>
        /// <returns>Para przekazanych argumentów</returns>
        protected virtual KeyValuePair<string, string> MakePair(string key, string value)
        {
            return new KeyValuePair<string, string>(key, value);
        }

        /// <summary>
        /// Metoda pomocnicza, wywołująca żądania HTTP GET na rzecz obiektu <see cref="IBlipCaller"/>.
        /// </summary>
        /// <param name="callback">Delegat wywoływany po otrzymaniu odpowiedzi</param>
        /// <param name="url">Adres żądania</param>
        /// <param name="auth">Prawda, gdy operacja wymaga uwierzytelnienia</param>
        /// <returns>Prawda, gdy żądanie zostało wysłane do serwisu Blip</returns>
        protected virtual bool CallGet(BlipResponse callback, string url, bool auth)
        {
            Uri uri = MakeUrl(url);
            return api.Get(callback, uri, auth ? authInfo : null);
        }

        /// <summary>
        /// Metoda pomocnicza, wywołująca żądania HTTP POST na rzecz obiektu <see cref="IBlipCaller"/>.
        /// </summary>
        /// <param name="callback">Delegat wywoływany po otrzymaniu odpowiedzi</param>
        /// <param name="url">Adres żądania</param>
        /// <param name="auth">Prawda, gdy operacja wymaga uwierzytelnienia</param>
        /// <param name="data">Słownik par (klucz, wartość) danych przesyłanych w żądaniu</param>
        /// <returns>Prawda, gdy żądanie zostało wysłane do serwisu Blip</returns>
        protected virtual bool CallPost(BlipResponse callback, string url, bool auth, IDictionary<string, string> data)
        {
            Uri uri = MakeUrl(url);
            return api.Post(callback, uri, auth ? authInfo : null, data);
        }
                
        /// <summary>
        /// Metoda pomocnicza, wywołująca żądania HTTP PUT na rzecz obiektu <see cref="IBlipCaller"/>.
        /// </summary>
        /// <param name="callback">Delegat wywoływany po otrzymaniu odpowiedzi</param>
        /// <param name="url">Adres żądania</param>
        /// <param name="auth">Prawda, gdy operacja wymaga uwierzytelnienia</param>
        /// <param name="data">Słownik par (klucz, wartość) danych przesyłanych w żądaniu</param>
        /// <returns>Prawda, gdy żądanie zostało wysłane do serwisu Blip</returns>
        protected virtual bool CallPut(BlipResponse callback, string url, bool auth, IDictionary<string, string> data)
        {
            Uri uri = MakeUrl(url);
            return api.Put(callback, uri, auth ? authInfo : null, data);
        }

        /// <summary>
        /// Metoda pomocnicza, wywołująca żądania HTTP DELETE na rzecz obiektu <see cref="IBlipCaller"/>.
        /// </summary>
        /// <param name="callback">Delegat wywoływany po otrzymaniu odpowiedzi</param>
        /// <param name="url">Adres żądania</param>
        /// <param name="auth">Prawda, gdy operacja wymaga uwierzytelnienia</param>
        /// <returns>Prawda, gdy żądanie zostało wysłane do serwisu Blip</returns>
        protected virtual bool CallDelete(BlipResponse callback, string url, bool auth)
        {
            Uri uri = MakeUrl(url);
            return api.Delete(callback, uri, auth ? authInfo : null);
        }

        /// <summary>
        /// Metoda pomocnicza, wywołująca żądania POST i wysyłająca plik na rzecz obiektu <see cref="IBlipCaller"/>.
        /// </summary>
        /// <param name="callback">Delegat wywoływany po otrzymaniu odpowiedzi</param>
        /// <param name="url">Adres żądania</param>
        /// <param name="auth">Prawda, gdy operacja wymaga uwierzytelnienia</param>
        /// <param name="upload">Nazwa parametru i jej wartość, powiązana z przesyłanym zasobem</param>
        /// <param name="file">Adres pliku do wysłania</param>
        /// <param name="data">Słownik par (klucz, wartość) danych przesyłanych w żądaniu</param>
        /// <param name="progress">Delegat otrzymujący stan postępu wysyłania pliku</param>
        /// <returns>Prawda, gdy żądanie zostało wysłane do serwisu Blip</returns>
        protected virtual bool CallUpload(BlipResponse callback, string url, bool auth, KeyValuePair<string, string> upload, Uri file, IDictionary<string, string> data = null, UploadProgress progress = null)
        {
            Uri uri = MakeUrl(url);
            return api.Upload(callback, uri, auth ? authInfo : null, upload, file, data, progress);
        }

        /// <summary>
        /// Dołącza parametry dodane metodą <see cref="Include"/> do adresu żądania. Każdorazowe wywołanie resetuje wartość ciągu parametrów Include.
        /// </summary>
        /// <param name="url">Adres żądania</param>
        /// <returns>Nowy adres żądania z dołączonymi parametrami</returns>
        private Uri MakeUrl(string url)
        {
            StringBuilder sb = new StringBuilder(URL);
            sb.Append(url);

            if (include.Length > 0)
            {
                string includeString = include.Remove(include.Length - 1, 1).ToString();
                include.Clear();

                sb.Append(url.Contains("?") ? '&' : '?');
                sb.Append("include=");
                sb.Append(includeString);
            }

            return new Uri(sb.ToString());
        }

        #region IBlipAPI Methods

        public virtual void Include(params string[] add)
        {
            foreach(string inc in add)
                include.AppendFormat("{0},", inc);
        }

        public virtual bool GetAllStatuses(BlipResponse callback, int limit = 20, int? since = null)
        {
            string url;
            if (since.HasValue)
                url = string.Format("/statuses/{0}/all_since?limit={1}", since.Value, limit);
            else
                url = string.Format("/statuses/all?limit={0}", limit);
            return CallGet(callback, url, false);
        }

        public virtual bool GetUserStatuses(BlipResponse callback, string login, int limit = 20, int? since = null)
        {
            string url;
            if (since.HasValue)
                url = string.Format("/users/{0}/statuses/{1}/all_since?limit={2}", login, since.Value, limit);
            else
                url = string.Format("/users/{0}/statuses?limit={1}", login, limit);
            return CallGet(callback, url, false);
        }

        public virtual bool GetTags(BlipResponse callback, string tag, int limit = 20)
        {
            string url = string.Format("/tags/{0}?limit={1}", tag, limit);
            return CallGet(callback, url, false);
        }

        public virtual bool GetTagsSince(BlipResponse callback, string tag, int since)
        {
            string url = string.Format("/tags/{0}/since/{1}", tag, since);
            return CallGet(callback, url, false);
        }

        public virtual bool GetConnectedMovie(BlipResponse callback, int id)
        {
            string url = string.Format("/updates/{0}/movie", id);
            return CallGet(callback, url, false);
        }

        public virtual bool GetConnectedRecording(BlipResponse callback, int id)
        {
            string url = string.Format("/updates/{0}/recording", id);
            return CallGet(callback, url, false);
        }

        public virtual bool GetUpdatePictures(BlipResponse callback, int id)
        {
            string url = string.Format("/updates/{0}/pictures", id);
            return CallGet(callback, url, false);
        }

        public virtual bool GetPictures(BlipResponse callback, int limit = 20, int? since = null)
        {
            string url;
            if (since.HasValue)
                url = string.Format("/pictures/{0}/all_since?limit={1}", since.Value, limit);
            else
                url = string.Format("/pictures/all?limit={0}", limit);
            return CallGet(callback, url, false);
        }

        public virtual bool GetShortlinks(BlipResponse callback, int limit = 20, int? since = null)
        {
            string url;
            if (since.HasValue)
                url = string.Format("/shortlinks/{0}/all_since?limit={1}", since.Value, limit);
            else
                url = string.Format("/shortlinks/all?limit={0}", limit);
            return CallGet(callback, url, false);
        }

        public virtual bool GetBliposphere(BlipResponse callback)
        {
            string url = "/bliposphere";
            return CallGet(callback, url, false);
        }

        public virtual bool GetUser(BlipResponse callback, string login)
        {
            string url = string.Format("/users/{0}", login);
            return CallGet(callback, url, false);
        }

        public virtual bool GetAvatar(BlipResponse callback, string login)
        {
            string url = string.Format("/users/{0}/avatar", login);
            return CallGet(callback, url, false);
        }

        public virtual bool GetBackground(BlipResponse callback, string login)
        {
            string url = string.Format("/users/{0}/background", login);
            return CallGet(callback, url, false);
        }

        public virtual bool GetStatuses(BlipResponse callback, int limit = 20, int? since = null)
        {
            string url = string.Format("/statuses{0}?limit={1}", since.HasValue ? since.Value + "/since" : string.Empty, limit);
            return CallGet(callback, url, true);
        }

        public virtual bool AddStatus(BlipResponse callback, string text, Uri picture = null)
        {
            string url = "/statuses";
            var tab = Dictionary();
            tab["status[body]"] = text;
            if (picture != null)
            {
                var upload = MakePair("status[picture]", "picture.jpg");
                return CallUpload(callback, url, true, upload, picture, tab);
            }
            else
            {
                return CallPost(callback, url, true, tab);
            }
        }

        public virtual bool DelStatus(BlipResponse callback, int id)
        {
            string url = string.Format("/statuses/{0}", id);
            return CallDelete(callback, url, true);
        }

        public virtual bool AddUpdate(BlipResponse callback, string text, Uri picture = null)
        {
            string url = "/updates";
            var tab = Dictionary();
            tab["update[body]"] = text;
            if (picture != null)
            {
                var upload = MakePair("update[picture]", "picture.jpg");
                return CallUpload(callback, url, true, upload, picture, tab);
            }
            else
            {
                return CallPost(callback, url, true, tab);
            }
        }

        public virtual bool GetAllUpdates(BlipResponse callback, int limit = 20, int? since = null)
        {
            string url;
            if(since.HasValue) 
                url = string.Format("/updates/{0}/all_since?limit={1}", since.HasValue ? since.Value.ToString() : string.Empty, limit);
            else
                url = string.Format("/updates/all?limit={0}", limit);
            return CallGet(callback, url, true);
        }

        public virtual bool GetUpdates(BlipResponse callback, int limit = 20)
        {
            string url = string.Format("/updates?limit={0}", limit);
            return CallGet(callback, url, true);
        }

        public virtual bool GetUpdatesSince(BlipResponse callback, int since)
        {
            string url = string.Format("/updates/{0}/since", since);
            return CallGet(callback, url, true);
        }

        public virtual bool GetUserUpdates(BlipResponse callback, string login, int limit = 20)
        {
            string url = string.Format("/users/{0}/updates?limit={1}", login, limit);
            return CallGet(callback, url, true);
        }

        public virtual bool GetUserUpdatesSince(BlipResponse callback, string login, int since)
        {
            string url = string.Format("/users/{0}/updates/{1}/since", login, since);
            return CallGet(callback, url, true);
        }

        public virtual bool GetUpdate(BlipResponse callback, int id)
        {
            string url = string.Format("/updates/{0}", id);
            return CallGet(callback, url, true);
        }

        public virtual bool DelUpdate(BlipResponse callback, int id)
        {
            string url = string.Format("/updates/{0}", id);
            return CallDelete(callback, url, true);
        }

        public virtual bool AddDirectedMessage(BlipResponse callback, string recipientLogin, string text, Uri picture = null)
        {
            string url = "/directed_messages";
            var tab = Dictionary();
            tab["directed_message[body]"] = text;
            tab["directed_message[recipient]"] = recipientLogin;
            if (picture != null)
            {
                var upload = MakePair("directed_message[picture]", "picture.jpg");
                return CallUpload(callback, url, true, upload, picture, tab);
            }
            else
            {
                return CallPost(callback, url, true, tab);
            }
        }

        public virtual bool AddDirectedMessage(BlipResponse callback, int recipientId, string text, Uri picture = null)
        {
            return AddDirectedMessage(callback, recipientId.ToString(), text, picture);
        }

        public virtual bool GetDirectedMessages(BlipResponse callback, int limit = 20)
        {
            string url = string.Format("/directed_messages?limit={0}", limit);
            return CallGet(callback, url, true);
        }

        public virtual bool GetDirectedMessagesSince(BlipResponse callback, int since)
        {
            string url = string.Format("/directed_messages/{0}/since", since);
            return CallGet(callback, url, true);
        }

        public virtual bool GetAllDirectedMessages(BlipResponse callback, int limit = 20, int? since = null)
        {
            string url;
            if (since.HasValue)
                url = string.Format("/directed_messages/{0}/all_since?limit={1}", since.Value, limit);
            else
                url = string.Format("/directed_messages/all?limit={0}", limit);
            return CallGet(callback, url, true);
        }

        public virtual bool GetUserDirectedMessages(BlipResponse callback, string login, int limit = 20)
        {
            string url = string.Format("/users/{0}/directed_messages?limit={1}", login, limit);
            return CallGet(callback, url, true);
        }

        public virtual bool GetUserDirectedMessagesSince(BlipResponse callback, string login, int since)
        {
            string url = string.Format("/users/{0}/directed_messages/{1}/since", login, since);
            return CallGet(callback, url, true);
        }

        public virtual bool GetDirectedMessage(BlipResponse callback, int id)
        {
            string url = string.Format("/directed_messages/{0}", id);
            return CallGet(callback, url, true);
        }

        public virtual bool DelDirectedMessage(BlipResponse callback, int id)
        {
            string url = string.Format("/directed_message/{0}", id);
            return CallDelete(callback, url, true);
        }

        public virtual bool GetPrivateMessages(BlipResponse callback, int limit = 20)
        {
            string url = string.Format("/private_messages?limit={0}", limit);
            return CallGet(callback, url, true);
        }

        public virtual bool GetPrivateMessagesSince(BlipResponse callback, int since)
        {
            string url = string.Format("/private_messages/since/{0}", since);            
            return CallGet(callback, url, true);
        }

        public virtual bool GetPrivateMessage(BlipResponse callback, int id)
        {
            string url = string.Format("/private_messages/{0}", id);
            return CallGet(callback, url, true);
        }

        public virtual bool DelPrivateMessage(BlipResponse callback, int id)
        {
            string url = string.Format("/private_messages/{0}", id);
            return CallDelete(callback, url, true);
        }

        public virtual bool AddPrivateMessage(BlipResponse callback, string recipientLogin, string text, Uri picture = null)
        {
            string url = "/private_messages";
            var tab = Dictionary();
            tab["private_message[body]"] = text;
            tab["private_message[recipient]"] = recipientLogin;
            if (picture != null)
            {
                var upload = MakePair("private_message[picture]", "picture.jpg");
                return CallUpload(callback, url, true, upload, picture, tab);
            }
            else
            {
                return CallPost(callback, url, true, tab);
            }
        }

        public virtual bool AddPrivateMessage(BlipResponse callback, int recipientId, string text, Uri picture = null)
        {
            return AddPrivateMessage(callback, recipientId.ToString(), text, picture);
        }

        public virtual bool GetNotices(BlipResponse callback, int limit = 20)
        {
            string url = string.Format("/notices?limit={0}", limit);
            return CallGet(callback, url, true);
        }

        public virtual bool GetNoticesSince(BlipResponse callback, int since)
        {
            string url = string.Format("/notices/since/{0}", since);
            return CallGet(callback, url, true);
        }

        public virtual bool GetAllNotices(BlipResponse callback, int limit = 20, int? since = null)
        {
            string url;
            if (since.HasValue)
                url = string.Format("/notices/{0}/all_since?limit={1}", since.Value, limit);
            else
                url = string.Format("/notices/all?limit={0}", limit);
            return CallGet(callback, url, true);
        }

        public virtual bool GetUserNotices(BlipResponse callback, string login, int limit = 20)
        {
            string url = string.Format("/users/{0}/notices?limit={1}", login, limit);
            return CallGet(callback, url, true);
        }

        public virtual bool GetUserNoticesSince(BlipResponse callback, string login, int since)
        {
            string url = string.Format("/users/{0}/notices/{1}/since", login, since);
            return CallGet(callback, url, true);
        }

        public virtual bool GetNotice(BlipResponse callback, int id)
        {
            string url = string.Format("/notices/{0}", id);
            return CallGet(callback, url, true);
        }

        public virtual bool CreateShortlink(BlipResponse callback, string link)
        {
            string url = "/shortlinks";
            var tab = Dictionary();
            tab["shortlink[original_link]"] = link;
            return CallPost(callback, url, true, tab);
        }

        public virtual bool GetDashboard(BlipResponse callback, string login = null, int? since = null)
        {
            string url;
            if (since.HasValue)
            {
                if (login != null)
                    url = string.Format("/users/{0}/dashboard/since/{1}", login, since.Value);
                else
                    url = string.Format("/dashboard/since/{0}", since.Value);
            }
            else
            {
                if (login != null)
                    url = string.Format("/users/{0}/dashboard", login);
                else
                    url = "/dashboard";
            }
            return CallGet(callback, url, true);
        }

        public virtual bool GetMyBliposphere(BlipResponse callback)
        {
            return CallGet(callback, "/bliposphere", true);
        }

        public virtual bool GetProfile(BlipResponse callback)
        {
            return CallGet(callback, "/profile", true);
        }

        public virtual bool GetSubscriptions(BlipResponse callback)
        {
            return CallGet(callback, "/subscriptions", true);
        }

        public virtual bool GetSubscriptionsFrom(BlipResponse callback)
        {
            return CallGet(callback, "/subscriptions/from", true);
        }

        public virtual bool GetSubscriptionsTo(BlipResponse callback)
        {
            return CallGet(callback, "/subscriptions/to", true);
        }

        public virtual bool GetUserSubscriptions(BlipResponse callback, string login)
        {
            string url = string.Format("/users/{0}/subscriptions", login);
            return CallGet(callback, url, true);
        }

        public virtual bool GetUserSubscriptionsFrom(BlipResponse callback, string login)
        {
            string url = string.Format("/users/{0}/subscriptions/from", login);
            return CallGet(callback, url, true);
        }

        public virtual bool GetUserSubscriptionsTo(BlipResponse callback, string login)
        {
            string url = string.Format("/users/{0}/subscriptions/to", login);
            return CallGet(callback, url, true);
        }

        public virtual bool DelSubscriptions(BlipResponse callback, string login)
        {
            string url = string.Format("/subscriptions/{0}", login);
            return CallDelete(callback, url, true);
        }

        public virtual bool AddSubscriptions(BlipResponse callback, string login, bool traceWWW = false, bool traceIM = false)
        {
            string url = string.Format("/subscriptions/{0}", login);
            var tab = Dictionary();
            tab["subscription[www]"] = traceWWW.ToString();
            tab["subscription[im]"] = traceIM.ToString();
            return CallPut(callback, url, true, tab);
        }

        public virtual bool SetAvatar(BlipResponse callback, Uri avatar, UploadProgress progress = null)
        {
            var upload = MakePair("avatar[file]", "avatar.jpg");
            return CallUpload(callback, "/avatar", true, upload, avatar, null, progress);
        }

        public virtual bool DelAvatar(BlipResponse callback)
        {
            return CallDelete(callback, "/avatar", true);
        }

        public virtual bool SetBackground(BlipResponse callback, Uri background, UploadProgress progress = null)
        {
            var upload = MakePair("background[file]", "background.jpg");
            return CallUpload(callback, "/background", true, upload, background, null, progress);
        }

        public virtual bool DelBackground(BlipResponse callback)
        {
            return CallDelete(callback, "/background", true);
        }

        #endregion
    }
}